package com.bwie.service.impl;
import cn.hutool.core.date.DateUtil;
import com.bwie.common.constants.LottoConstants;
import com.bwie.common.constants.TokenConstants;
import com.bwie.common.domain.*;
import com.bwie.common.domain.request.LottoLogListReq;
import com.bwie.common.domain.response.DrawResp;
import com.bwie.common.result.PageResult;
import com.bwie.common.utils.JwtUtils;
import com.bwie.exception.LoginException;
import com.bwie.mapper.LottoMapper;
import com.bwie.mapper.SignMapper;

import com.bwie.service.LottoService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @className: LottoServiceImpl
 * @Description: TODO
 * @version: v1.８.0
 * @author: 刘鑫
 * @date: 2025/8/31 17:46
 */
@Service
public class LottoServiceImpl implements LottoService {
    @Autowired
    private LottoMapper lottoMapper;

    @Autowired
    private HttpServletRequest request;
    @Autowired
    private SignMapper signMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public List<Lotto> lottoList(Lotto lotto) {
        List<Lotto> lottos = lottoMapper.lottoList(lotto);
        return lottos;
    }

    @Override
    public List<LottoLog> lottoLogList(LottoLogListReq lottoLogListReq) {
        return lottoMapper.lottoLogList(lottoLogListReq);
    }

    /**
     * 新增
     * @param lotto
     */
    @Override
    public void add(Lotto lotto) {
        Integer consumePoints = lotto.getConsumePoints();
        //10、消耗积分只能为整数不可小于0
        if (consumePoints<=0){
            throw new LoginException(LottoConstants.CONSUMEPOINT_IS_NOT_ERROR,LottoConstants.CONSUMEPOINT_IS_NOT_ERROR_MSG);
        }
        //获取中奖名单
        String dicWinningList = lotto.getDicWinningList();
        //获取中奖记录
        String dicWinningLog = lotto.getDicWinningLog();
        //获取活动规则
        String dicActRule = lotto.getDicActRule();

        boolean winningList = Boolean.parseBoolean(dicWinningList);
        boolean winningLog = Boolean.parseBoolean(dicWinningLog);
        boolean actRule = Boolean.parseBoolean(dicActRule);
        //名单
        int roster = winningList ? 1 : 0;
        //记录
        int log = winningLog ? 1 : 0;
        //规则
        int rule = actRule ? 1 : 0;
        //给数据赋值
        lotto.setDicWinningList(String.valueOf(roster));
        lotto.setDicWinningLog(String.valueOf(log));
        lotto.setDicActRule(String.valueOf(rule));
        lottoMapper.add(lotto);
    }

    /**
     * 抽奖
     * @param lottoId
     * @return
     */
    @Transactional
    /**
     * 抽奖主流程
     * 1. 校验用户登录
     * 2. 分布式锁防止重复抽
     * 3. 校验活动/积分/次数
     * 4. 核心抽奖逻辑
     * 5. 扣积分、写日志、发MQ
     * 6. 必释放锁
     */
    @Override
    public DrawResp draw(Integer lottoId) {
        /* ---------------- 1. 获取当前登录用户 ---------------- */
        String token = request.getHeader(TokenConstants.TOKEN);
        String userKey = JwtUtils.getUserKey(token);
        User user = (User) redisTemplate.opsForValue().get(TokenConstants.LOGIN_TOKEN_KEY + userKey);
        if (user == null) {
            throw new LoginException(1001, "用户未登录");
        }

        String lockKey = "lotto:draw:" + lottoId + ":" + user.getUserId();

        /* ---------------- 2. 拿锁（循环+重试） ---------------- */
        int retries = 0;
        while (!Boolean.TRUE.equals(
                redisTemplate.opsForValue().setIfAbsent(lockKey, "1", Duration.ofSeconds(30)))) {
            if (++retries > 10) {
                throw new RuntimeException("系统繁忙，请稍后再试");
            }
            try {
                Thread.sleep(50);          // 稍等再冲
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        }

        /* ---------------- 3. 业务逻辑（带原始中文注释） ---------------- */
        try {
            /* 3.1 查询活动信息 */
            Lotto lotto = lottoMapper.selectLottoInfo(lottoId);
            if (lotto == null || lotto.getStatus() == 2) {
                throw new LoginException(1002, "活动已失效");
            }

            /* 3.2 查询用户剩余积分 */
            int leftPoints = lottoMapper.selectIntergrateByUserId(user.getUserId());
            if (leftPoints < lotto.getConsumePoints()) {
                throw new LoginException(1003, "您的积分不足");
            }

            /* 3.3 计算可抽次数并缓存到Redis，随后自减1 */
            int canDrawTimes = leftPoints / lotto.getConsumePoints();
            String countKey = "draw:" + lottoId + ":" + user.getUserId();
            redisTemplate.opsForValue().set(countKey, canDrawTimes);
            redisTemplate.opsForValue().decrement(countKey, 1);
            int leftTimes = (Integer) redisTemplate.opsForValue().get(countKey);

            DrawResp drawResp = new DrawResp();
            drawResp.setCount(leftTimes);

            /* 3.4 核心抽奖：生成0~1随机数，匹配奖品概率 */
            double luckyNumber = ThreadLocalRandom.current().nextDouble();
            Prize prize = lottoMapper.selectPrizeInfo(lottoId, luckyNumber);

            /* 3.5 封装抽奖日志 */
            LottoLog lottoLog = new LottoLog();
            lottoLog.setLottoId(lottoId);
            lottoLog.setUserId(user.getUserId());
            lottoLog.setUserPhone(user.getUserPhone());
            lottoLog.setUserWechat(user.getUserWechat());
            lottoLog.setLottoLogTime(new Date());

            if (prize == null) {
                /* 未中奖 */
                drawResp.setMessage("很遗憾，您未抽中奖品，还剩" + drawResp.getCount() + "次");
                lottoLog.setWinStatus(0);
                lottoLog.setWinningPrize("unlucky");
            } else {
                /* 中奖处理、改日志） */
                handleNoWinCase(prize, drawResp, lottoLog, lockKey);
                lottoLog.setWinningPrize("lucky");
            }

            /* 3.6 扣除本次消耗积分 */
            lottoMapper.updateUserInfoPoints(lotto.getConsumePoints(), user.getUserId());

            /* 3.7 发送MQ（失败只打日志，不影响主流程） */
            try {
                rabbitTemplate.convertAndSend("addLog", lottoLog);
            } catch (AmqpException e) {
                /* 发送失败仅记录，不抛异常 */
                System.err.println("发送MQ失败：" + lottoLog);
                e.printStackTrace();
            }

            return drawResp;

        } finally {
            /* 3.8 必释放锁，防止锁泄漏 */
            redisTemplate.delete(lockKey);
        }
    }



    /**
     * 处理中奖逻辑
     * @param prize      抽中的奖品
     * @param drawResp   返回前端的对象
     * @param lottoLog   抽奖日志
     * @param lockKey    分布式锁键（仅做日志打印，不删锁）
     */
    public void handleNoWinCase(Prize prize, DrawResp drawResp, LottoLog lottoLog, String lockKey) {
        /* 1. 库存校验 */
        if (prize.getRemainingCount() == null || prize.getRemainingCount() < 1) {
            // 注意：锁不能在这里删，由外层 finally 统一释放
            throw new LoginException(10001, "库存不足");
        }

        /* 2. 给用户提示 */
        drawResp.setMessage("恭喜你抽中了 " + prize.getPrizeName() + "，还剩" + drawResp.getCount() + "次抽奖机会");

        /* 3. 如果是积分奖品，给用户加积分 */
        if (Arrays.asList(3, 7, 11, 14, 18).contains(prize.getPrizeId())) {
            String token = request.getHeader(TokenConstants.TOKEN);
            String userKey = JwtUtils.getUserKey(token);
            String userRedisKey = TokenConstants.LOGIN_TOKEN_KEY + userKey;
            User user = (User) redisTemplate.opsForValue().get(userRedisKey);
            if (user != null) {
                lottoMapper.insertUserInfoWincase(user.getUserId());
            }
        }

        /* 4. 原子扣库存（SQL 里做 remaining_count = remaining_count - 1） */
        int affected = lottoMapper.decreaseInventory(prize.getPrizeId());
        if (affected == 0) {
            throw new LoginException(10001, "库存不足");
        }

        /* 5. 标记中奖日志 */
        lottoLog.setWinStatus(1);
        lottoLog.setPrizeId(prize.getPrizeId());
    }

    /**
     * 签到列表分页
     * @param signIn
     * @return
     */
    @Override
    public PageResult<SignIn> signList(SignIn signIn) {
        PageHelper.startPage(signIn.getPageNum(),signIn.getPageSize());
        List<SignIn> signIns = signMapper.selectSignListInfo(signIn);
        PageInfo<SignIn> signInPageInfo = new PageInfo<>(signIns);

        return PageResult.toResult(signInPageInfo.getTotal(),signIns).getData();
    }

    @Override
    public DrawResp sign() {
        //获取用户数据
        String token = request.getHeader(TokenConstants.TOKEN);
        String userKey = JwtUtils.getUserKey(token);
        User user = (User) redisTemplate.opsForValue().get(TokenConstants.LOGIN_TOKEN_KEY + userKey);
        DrawResp drawResp = new DrawResp();

        //查询当天是否签到
        LocalDate nowDate = LocalDate.now();
        Integer i = signMapper.selectyestDateDay(nowDate,user.getUserId());
        if (i!=null&&i>0){
          drawResp.setMessage("您已经签到过了");
          return drawResp;
        }
        //查询昨天是否签到
        LocalDate yestDateDay = nowDate.minusDays(1);
        Integer yestDayCount = signMapper.selectyestDateDay(yestDateDay, user.getUserId());
        //查询最近一次签到记录
        SignIn sign = signMapper.selectLatesSignByUserId(user.getUserId());
        //默认从今天开始
        int countDay=1;
        if (yestDayCount!=null&&yestDayCount>0&&sign!=null){
            countDay=sign.getContinuousDays()+1;
        }
        //关联实体类 构造本次签到记录
        SignIn signIn = new SignIn();
        //生成随机数10-100
        Random random = new Random();
        int num = random.nextInt(91) + 10;
         signIn.setUserId(user.getUserId());
         signIn.setSignDate(nowDate);
         signIn.setUserName(user.getUserName());
         signIn.setPoints(num);
         signIn.setContinuousDays(countDay);
         //插入签到记录
        signMapper.insertSign(signIn);
        //更新用户数据积分
        signMapper.updateUserInfoPoints(user.getUserId(),num);
        // 返回响应
        drawResp.setMessage("签到成功！连续签到"+countDay+"天，获得"+num+"积分");
        return drawResp;
    }

    @Override
    public List<Prize> findAll() {

        return lottoMapper.findAll();
    }

}
